package Client.Net;

import Library.*;
import Library.Events.*;
import Library.Game.*;
import Library.Login.*;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.*;

/**
 * Client Service zorgt voor het beheren van de GameServer en LoginServer
 * verbindingen.
 */
public class ClientService implements IClientService
{
    // Login Server
    protected transient ClientFactory m_LoginFactory;
    protected transient ILoginGateway m_LoginServer;
    protected transient ITicket m_Ticket;
    
    // Game Server
    protected transient ClientFactory m_GameFactory;
    protected transient IGameServerGateway m_GameServer;
    protected transient ISession m_Session;
    protected transient IGameInfo m_Game; //TODO: [CLIENT#03] [CHECK] mag deze zichtbaar zijn?? moet toch via de m_Session alles doen??

    protected transient ICommunication m_Form;
    protected transient int m_TurnDecission = TurnDecission.Undecided;
    
    /**
     * LoginServer Acties
     */
    
    /**
     * Constructor voor Client Service: Zet een verbinding op met de LoginServer.
     * @param loginip ip-adres van de login server
     * @param port poortnr
     * @throws RemoteException Remote Error
     * @throws NotBoundException Verbinding Error
     * @throws Exception Generieke Error
     */
    public ClientService(String loginip, int port, ICommunication form) throws RemoteException, NotBoundException, Exception
    {
        m_LoginFactory = new ClientFactory(loginip, port);
        m_LoginServer = m_LoginFactory.GetRemoteInstance("Gateway");
        m_Form = form;
    }

    public IGameInfo getGame()
    {
        return m_Game;
    }

    /**
     * De LoginServer en eventueel de GameServer worden op de hoogte gesteld
     * dat de client wil uitloggen.
     */
    public void logout()
    {
        // Huideige Game verlaten (als deze bestaat)
        if (m_Session != null)
        {
            try
            {
                m_Session.leaveGame();
                m_Session = null;
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
        
        // Uitloggen LoginServer (als er ingelogd is)
        if (m_Ticket != null)
        {
            try
            {
                m_LoginServer.logout(m_Ticket);
                m_Ticket = null;
            }
            catch (Exception ex)
            {
                ex.printStackTrace();
            }
        }
    }
    
    /**
     * Logt in op de Login Server en slaat de ticket op.
     * @param username gebruikersnaam
     * @param password wachtwoord
     * @return succesvol
     */
    public int login(String username, int password) throws RemoteException, Exception
    {
	ActionResult<ITicket> result = m_LoginServer.login(username, password);
	m_Ticket = result.getResult() == ErrorCodes.ERROR_NOERROR ? result.getObject() : null; 
        return result.getResult();
    }
    
    /**
     * Maakt een nieuw account aan.
     * @param username gebruikersnaam
     * @param password wachtwoord
     * @return succesvol
     */
    public int createAccount(String username, int password) throws RemoteException, Exception
    {
        return m_LoginServer.createAccount(username, password);
    }
    
    public Map<String, String> getGameServers() throws RemoteException
    {
        //TODO: [CLIENT#04] [CHECK] mag dit?? IGameServer staat op 'GameServer'-App.
        if(m_Ticket != null)
        {
            return m_Ticket.getGameServers();
        }
        else
        {
            return null;
        }
    }
    
    /**
     * GameServer Acties
     */

    /**
     * Probeert een verbinding te maken met een gameserver.
     * @param gameserverip ip-adres van de gameserver
     * @param port poortnr van de gameserver
     * @return succesvol
     * @throws RemoteException Remote Error
     */
    public boolean connect(String gameserverip, int port) throws RemoteException
    {
        try
        {
            m_GameFactory = new ClientFactory(gameserverip, port);
            m_GameServer = m_GameFactory.GetRemoteInstance("Gateway");
            return true;
        }
        catch(Exception ex)
        {
            ex.printStackTrace();
        }
        return false;
    }
    
    /**
     * Maakt een nieuwe game aan op de gameserver en joint deze.
     * @param name game name
     * @return succesvol
     */
    public int createGame(String name, int maxplayers, int mapid, int gametime, int turntime, int armysize) throws RemoteException, Exception
    {
        m_GameFactory.MakeRemote(this, true);
        ActionResult<IGameInfo> result = m_GameServer.createGame(name, maxplayers, mapid, gametime, turntime, armysize);
        if (result.getResult() == ErrorCodes.ERROR_NOERROR)
        {
            m_Game = result.getObject();
            ActionResult<ISession> result2 = m_GameServer.join(m_Game, m_Ticket, this);
            if (result2.getResult() == ErrorCodes.ERROR_NOERROR)
                m_Session = result2.getObject();
            return result2.getResult();
        }
        return result.getResult();
    }
    
    /**
     * Joint een game
     * @param name game name
     * @return succesvol
     */
    public int joinGame(String game) throws RemoteException
    {
        try
        {
            m_Game = m_GameServer.findGame(game);
            if (m_Game != null)
            {
		m_GameFactory.MakeRemote(this, true);
                ActionResult<ISession> result = m_GameServer.join(m_Game, m_Ticket, this);
                if (result.getResult() == ErrorCodes.ERROR_NOERROR)
                    m_Session = result.getObject();
                return result.getResult();
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
            return ErrorCodes.ERROR_UNKNOWN;
        }
        //TODO: [CLIENT#06] [TRANSIENT] IGame should not be accessed like this
        //TODO: [CLIENT#07] [MISSING] GameServer.join(String name) or by id
        return ErrorCodes.ERROR_GAME_NOT_FOUND;
    }
    
    public boolean startGame() throws RemoteException, Exception
    {
        if (m_Session != null)
        {
            return m_Session.startGame();
        }
        return false;
    }
    
    /**
     * Vraagt een lijst met beschikbare games op van de gameserver.
     * @return game lijst
     */
    public List<IGameInfo> getGames() throws RemoteException, Exception
    {
        return m_GameServer.getGameInfos();
    }
    
    /**
     * Speler Game Acties
     */
    
    /**
     * 
     * @return 
     */
    public void leaveGame() throws RemoteException, Exception
    {
        m_Session.leaveGame();
    }
    
    //TODO: [CLIENT#08] [FUTURE] More game actions here
    
    /**
     * Omruimen van resources
     */
    public void dispose()
    {
	// Leave Current Game + Destroy Ticket
	logout();
	
        if (m_LoginFactory != null)
        {
            m_LoginFactory.dispose();
            m_LoginFactory = null;
        }
        
        if (m_GameFactory != null)
        {
            m_GameFactory.dispose();
            m_GameFactory = null;
        }
    }
    
    public int endTurn()
    {
        if (m_Session == null)
            return ErrorCodes.ERROR_NO_ACTIVE_GAME;
        else
        {
            try
            {
                return m_Session.endTurn();
            }
            catch (RemoteException ex)
            {
                ex.printStackTrace();
		return ErrorCodes.ERROR_UNKNOWN;
            }
        }
    }
    
    public List<IPlayer> getPlayers() throws RemoteException, Exception
    {
        return m_Session.getPlayers();
    }
    
    public int turnDecission(int decission) throws RemoteException, Exception
    {
	if (m_Session != null)
	{
	    int result = m_Session.turnDecission(decission);
	    if (result == ErrorCodes.ERROR_NOERROR)
		m_TurnDecission = decission;
	    return result;
	}
	return ErrorCodes.ERROR_NO_ACTIVE_GAME;
    }
    
    public ITicket getTicket()
    {
        return m_Ticket;
    }
    
    public int getTurnDecission()
    {
	return m_TurnDecission;
    }
    
    public int reinforce(int landID, int amount)
    {
	if (m_Session != null)
	{
	    try
	    {
		return m_Session.reinforce(landID, amount);
	    }
	    catch (RemoteException ex)
	    {
		return ErrorCodes.ERROR_UNKNOWN;
	    }
	    catch (Exception ex)
	    {
		return ErrorCodes.ERROR_UNKNOWN;
	    }
	}
	return ErrorCodes.ERROR_NO_ACTIVE_GAME;
    }
    
    public int attack(int landID1, int landID2, int amount) throws RemoteException, Exception
    {
	if (m_Session == null)
	    return ErrorCodes.ERROR_NO_ACTIVE_GAME;
	
	return m_Session.attack(landID1, landID2, amount);
    }
    
    public int defend(int amount) throws RemoteException, Exception
    {
	if (m_Session == null)
	    return ErrorCodes.ERROR_NO_ACTIVE_GAME;
	return m_Session.defend(amount);
    }
    
    // Callbacks
    public final Event<MoveEvent> onMoveArmies = new Event<MoveEvent>();
    public void onMoveArmies(int aantal, int landvanId, int landnaarId) throws RemoteException, Exception
    {
	onMoveArmies.trigger(new MoveEvent(this, 1, null, landvanId, aantal));
    }

    public final Event<ValueEvent<IPlayer>> onGameTurnChanged = new Event<ValueEvent<IPlayer>>();
    public void onGameTurnChanged(IPlayer player) throws RemoteException, Exception
    {
	onGameTurnChanged.trigger(new ValueEvent<IPlayer>(this, 1, null, player));
    }

    public final Event<ValueEvent<String>> onMessage = new Event<ValueEvent<String>>();
    public void onMessage(String message) throws RemoteException, Exception
    {
	onMessage.trigger(new ValueEvent<String>(this, 1, null, message));
    }

    public final Event<ValueEvent<Integer>> onGameStatusChanged = new Event<ValueEvent<Integer>>();
    public void onGameStatusChanged(int status) throws RemoteException, Exception 
    {
	onGameStatusChanged.trigger(new ValueEvent<Integer>(this, 1, null, status));
    }

    public final Event<ValueEvent<IPlayer>> onPlayerJoined = new Event<ValueEvent<IPlayer>>();
    public void onPlayerJoined(IPlayer player) throws RemoteException, Exception
    {
	onPlayerJoined.trigger(new ValueEvent<IPlayer>(this, 1, null, player));
    }

    public final Event<ValueEvent<IPlayer>> onPlayerLeft = new Event<ValueEvent<IPlayer>>();
    public void onPlayerLeft(IPlayer player) throws RemoteException, Exception
    {
	onPlayerLeft.trigger(new ValueEvent<IPlayer>(this, 1, null, player));
    }

    public final Event<LandOwnerEvent> onSetLandOwner = new Event<LandOwnerEvent>();
    public void onSetLandOwner(int landId, IPlayer plr) throws RemoteException, Exception
    {
	onSetLandOwner.trigger(new LandOwnerEvent(this, 1, null, plr, landId));
    }

    public final Event<DecissionEvent> onPlayerDecided = new Event<DecissionEvent>();
    public void onPlayerDecided(IPlayer player, int decission) throws RemoteException, Exception
    {
	onPlayerDecided.trigger(new DecissionEvent(this, 1, null, player, decission));
    }


    public final Event<TimeEvent> onPlacementTimerEvent = new Event<TimeEvent>();
    public void onPlacementTimerEvent(int time, int totaltime) throws RemoteException, Exception
    {
	onPlacementTimerEvent.trigger(new TimeEvent(this, 1, null, time, totaltime));
    }

    public final Event<TimeEvent> onGameTimerEvent = new Event<TimeEvent>();
    public void onGameTimerEvent(int time, int totaltime) throws RemoteException, Exception
    {
	onGameTimerEvent.trigger(new TimeEvent(this, 1, null, time, totaltime));
    }

    public final Event<TimeEvent> onTurnTimerEvent = new Event<TimeEvent>();
    public void onTurnTimerEvent(int time, int totaltime) throws RemoteException, Exception
    {
	onTurnTimerEvent.trigger(new TimeEvent(this, 1, null, time, totaltime));
    }

    public final Event<ArmySizeEvent> onUpdateAvailableArmies = new Event<ArmySizeEvent>();
    public void onUpdateAvailableArmies(int amount, int bonusAmount) throws RemoteException, Exception
    {
	onUpdateAvailableArmies.trigger(new ArmySizeEvent(this, 1, null, amount, bonusAmount));
    }

    public final Event<TimeEvent> onDefendTimerEvent = new Event<TimeEvent>();
    public void onDefendTimerEvent(int time, int totaltime) throws RemoteException, Exception
    {
	onDefendTimerEvent.trigger(new TimeEvent(this, 1, null, time, totaltime));
    }

    public final Event<ArmyPlacementEvent> onArmiesPlaced = new Event<ArmyPlacementEvent>();
    public void onArmiesPlaced(IPlayer player, int landID, int totalArmies, int newArmies) throws RemoteException, Exception
    {
	onArmiesPlaced.trigger(new ArmyPlacementEvent(this, 1, null, player, landID, totalArmies, newArmies));
    }

    public final Event<AttackEvent> onAttack = new Event<AttackEvent>();
    public void onAttack(IPlayer attPlayer, int attLandId, List<Integer> attDices, int lostAttArmies, IPlayer defPlayer, 
	    int defLandId, List<Integer> defDices, int lostDefArmies) throws RemoteException, Exception
    {
	onAttack.trigger(new AttackEvent(this, 1, null, 
		attPlayer, attLandId, attDices, lostAttArmies, 
		defPlayer, defLandId, defDices, lostDefArmies));
    }

    public final Event<DefensiveEvent> onDefending = new Event<DefensiveEvent>();
    public void onDefending(IPlayer attacker, int attLandID, int amount, int defLandID, int recDefAmount) throws RemoteException, Exception
    {
	onDefending.trigger(new DefensiveEvent(this, 1, null, attacker, attLandID, amount, defLandID, recDefAmount));
    }
}
